package flare.query
{
  /**
   * Expression operator for text matching operations. Performs prefix,
   * suffix, containment, and regular expression matching.
   */
  public class Match extends BinaryExpression
  {
    /** Indicates a prefix matching test. */
    public static const PREFIX:int = 0;
    /** Indicates a suffix matching test. */
    public static const SUFFIX:int = 1;
    /** Indicates a string containment test. */
    public static const WITHIN:int = 2;
    /** Indicates a regular expression matching test. */
    public static const REGEXP:int = 3;

    /** Returns a string representation of the string matching operator. */
    public override function get operatorString():String
    {
      switch (_op)
      {
        case PREFIX: return 'STARTS-WITH';
        case SUFFIX: return 'ENDS-WITH';
        case WITHIN: return 'CONTAINS';

        case REGEXP: return 'REGEXP';
        default: return '?';
      }
    }

    /**
       * Create a new Match expression.
       * @param operation the operation to perform
       * @param left the left sub-expression
       * @param right the right sub-expression
       */
    public function Match(op:int, left:*, right:*)
    {
      super(op, PREFIX, REGEXP, left, right);
    }

    /**
     * @inheritDoc
     */
    public override function clone():Expression
    {
      return new Match(_op, _left.clone(), _right.clone());
    }

    /**
     * @inheritDoc
     */
    public override function eval(o:Object = null):*
    {
      var s:String = String(_left.eval(o));
      var p:String = String(_right.eval(o));

      // compute return value
      switch (_op)
      {
        case PREFIX: return StringUtil.startsWith(s, p);
        case SUFFIX: return StringUtil.endsWith(s, p);
        case WITHIN: return s.indexOf(p) >= 0;

        case REGEXP: return parseRegExp(p).test(s);
      }
      throw new Error("Unknown operation type: " + _op);
    }

    private var cachedRegExp:RegExp;
    private var cachedPattern:String;

    private function parseRegExp(p:String):RegExp
    {
      if(p == cachedPattern)return cachedRegExp;

      cachedPattern = p;
      var tok:Array = p.split("/");
      cachedRegExp = new RegExp(tok[1], tok[2]);
      return cachedRegExp;
    }

    // -- Static constructors ---------------------------------------------

    /**
     * Creates a new Match operator for matching string prefix.
     * @param left the left-hand input expression
     * @param right the right-hand input expression
     * @return the new Match operator
     */
    public static function StartsWith(left:*, right:*):Match
    {
      return new Match(PREFIX, left, right);
    }

    /**
     * Creates a new Match operator for matching a string suffix.
     * @param left the left-hand input expression
     * @param right the right-hand input expression
     * @return the new Match operator
     */
    public static function EndsWith(left:*, right:*):Match
    {
      return new Match(SUFFIX, left, right);
    }

    /**
     * Creates a new Match operator for matching string containment.
     * @param left the left-hand input expression
     * @param right the right-hand input expression
     * @return the new Match operator
     */
    public static function Contains(left:*, right:*):Match
    {
      return new Match(WITHIN, left, right);
    }

    /**
     * Creates a new Match operator for matching a regular expression.
     * @param left the left-hand input expression
     * @param right the right-hand input expression
     * @return the new Match operator
     */
    public static function RegEx(left:*, right:*):Match
    {
      return new Match(REGEXP, left, right);
    }
  } // end of class Match
}